package demo.javax.sql;

import com.alibaba.nacos.api.exception.NacosException;
import com.jucoin.infra.ds.DsBuilder;
import com.jucoin.infra.ds.DynamicHikariDatasource;
import com.jucoin.infra.vo.cfg.DatasourceCfg;
import com.zaxxer.hikari.HikariConfigMXBean;
import com.zaxxer.hikari.HikariDataSource;
import com.zaxxer.hikari.HikariPoolMXBean;
import demo.java.lang.ThreadDemo;
import demo.java.sql.ConnectionDemo;
import demo.java.sql.JdbcDemo;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.*;

public class DynamicHikariDatasourceTest {

    private static final Logger logger = LoggerFactory.getLogger(DynamicHikariDatasourceTest.class);

    private DynamicHikariDatasource ds;
    private DsBuilder dsBuilder;

    @Before
    public void init() throws NacosException {
        String appName = "infra-sdk-ds-test";
        String dataId = "infra-sdk-ds-test.yaml";
        dsBuilder = new DsBuilder("dev", appName);
        ds = dsBuilder.buildDs(dataId);
    }

    @After
    public void destroy() throws Exception {
        if (ds != null) {
            ds.close();
        }
        if (dsBuilder != null) {
            dsBuilder.close();
        }
    }

    @Test
    public void testDataSource() throws SQLException {
        try (Connection conn = ds.getConnection()) {
            // 执行SQL语句
            Statement statement = conn.createStatement();
            ResultSet resultSet = statement.executeQuery("SELECT * FROM alert_rule");
            JdbcDemo.printResultSet(resultSet);
        }
    }

    @Test
    public void testChangeJdbcUrl() {
        int i = 0;
        while (i < 10) {
            try (Connection conn = ds.getConnection()) {
                // 执行SQL语句
                Statement statement = conn.createStatement();
                ResultSet resultSet = statement.executeQuery("SELECT * FROM alert_rule");
                JdbcDemo.printResultSet(resultSet);
                printPoolStats(ds);
            } catch (Exception e) {
                e.printStackTrace();
            }
            i++;
            if (i == 5) {
                DatasourceCfg dsCfg = new DatasourceCfg();
                dsCfg.setJdbcUrl("jdbc:mysql://cex-mysql-ex-test-cluster.cluster-c5mgk4qm8m2z.ap-southeast-1.rds.amazonaws.com:3358/ops_alert");
                dsCfg.setUsername("ops_arthas_rw");
                dsCfg.setPassword("VYUmItYvMaQ2Tzdn");
                dsCfg.setDriverClassName("com.mysql.cj.jdbc.Driver");
                System.err.println("------------------ change jdbcUrl ------------------");
                ds.refreshDataSource(dsCfg);
            }
            ThreadDemo.safeSleep(500);
        }
    }

    /**
     * 测试2、通过Nacos配置中心，动态修改DS配置。
     */
    @Test
    public void testChangeDsByNacos() throws SQLException {
        int i = 0;
        while (i < 10000) {
            try (Connection conn = ds.getConnection();
                 Statement statement = conn.createStatement();) {
                // 执行SQL语句
                ResultSet resultSet = statement.executeQuery("SELECT * FROM alert_rule");
                JdbcDemo.printResultSet(resultSet);
                printPoolStats(ds);
            }
            ThreadDemo.safeSleep(1000);
            i++;
        }
    }

    /**
     * 测试1、DS关闭后，已获取的连接是否可用。
     * 结论：
     * 1. 已获取的连接，在DS关闭后，是不可用的。
     * 2. 已获取的连接，在DS关闭后，会抛出SQLNonTransientConnectionException异常。
     *
     * @throws SQLException
     */
    @Test
    public void testCloseDataSource1() throws SQLException {

        // 打印连接池状态
        startPrintPoolStatsAsync(ds);
        System.err.println("====================1. 正常获取连接，执行查询语句");
        // 执行SQL语句
        try (Connection conn = ds.getConnection();
             Statement statement = conn.createStatement()) {
            // 执行SQL语句
            ResultSet resultSet = statement.executeQuery("SELECT * FROM alert_rule");
            JdbcDemo.printResultSet(resultSet);
            printPoolStats(ds);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.err.println("===2. 先获取连接,再关闭连接池，执行查询语句");
        // 获取连接
        Connection conn = null;
        Statement statement = null;
        try {
            conn = ds.getConnection();
            statement = conn.createStatement();
            // 异步关闭连接池：关闭连接池，会断开所有空闲连接，并终止或关闭所有活动连接。
            ds.close();
            System.err.println("====关闭连接池，会断开所有空闲连接，并终止或关闭所有活动连接");
            ResultSet resultSet = statement.executeQuery("SELECT * FROM alert_rule");
            JdbcDemo.printResultSet(resultSet);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                statement.close();
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }


        ThreadDemo.safeSleep(1000);

    }

    /**
     * 测试2、验证DS关闭后，能否再次从连接池获取连接。
     * 结论：
     * 1. DS关闭后，再次从连接池获取连接，会抛出SQLException异常：HikariDataSource has been closed.。
     *
     * @throws SQLException
     */
    @Test
    public void testCloseDataSource2() throws Exception {
        // 打印连接池状态
        startPrintPoolStatsAsync(ds);
        System.err.println("====================1. 正常获取连接，执行查询语句");
        // 执行SQL语句
        try (Connection conn = ds.getConnection();
             Statement statement = conn.createStatement();) {
            // 执行SQL语句
            ResultSet resultSet = statement.executeQuery("SELECT * FROM alert_rule");
            JdbcDemo.printResultSet(resultSet);
            printPoolStats(ds);
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 关闭连接池
        ds.close();
        ThreadDemo.safeSleep(500);

        System.err.println("===  再次从连接池获取连接，执行查询语句");
        Connection conn = ds.getConnection();
        ConnectionDemo.testConnection(conn);


        ThreadDemo.safeSleep(1000);

    }

    public static void startPrintPoolStatsAsync(DynamicHikariDatasource ds) {
        Thread t = new Thread(
                () -> {
                    while (true) {
                        printPoolStats(ds);
                        ThreadDemo.safeSleep(200);
                    }
                });
        t.setDaemon(true);
        t.start();
    }

    /**
     * 关闭连接池，会断开所有空闲连接，并终止或关闭所有活动连接。
     *
     */
    public void closeDsAsync() {
        Thread t = new Thread(
                () -> {
                    try {
                        ds.close();
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                    System.err.println("====关闭连接池，会断开所有空闲连接，并终止或关闭所有活动连接");
                    printPoolStats(ds);
                });
        t.start();
    }

    public static void printPoolStats(DynamicHikariDatasource ds) {
        HikariDataSource dataSource = ds.getRealDataSource();
        HikariPoolMXBean poolMXBean = dataSource.getHikariPoolMXBean();
        String poolName = dataSource.getPoolName();
        String jdbcUrl = dataSource.getJdbcUrl();

        int totalConnections = poolMXBean.getTotalConnections();
        int activeConnections = poolMXBean.getActiveConnections();
        int idleConnections = poolMXBean.getIdleConnections();
        int threadsAwaitingConnection = poolMXBean.getThreadsAwaitingConnection();


        HikariConfigMXBean configMXBean = dataSource.getHikariConfigMXBean();
        logger.info("DS: {} ({}) 状态 (total={}, active={}, idle={}, waiting={})，配置：max={}, mini={}, timeout={}ms",
                poolName, jdbcUrl, totalConnections, activeConnections, idleConnections, threadsAwaitingConnection,
                configMXBean.getMaximumPoolSize(), configMXBean.getMinimumIdle(), configMXBean.getConnectionTimeout());

        System.err.println("=== HikariCP 连接池状态 ===");
        String logStr = String.format("DS：%s (%s), 状态：（total=%d, active=%d, idle=%d, waiting=%d），配置：（max=%s, mini=%s, timeout=%s ms）",
                poolName, jdbcUrl, totalConnections, activeConnections, idleConnections, threadsAwaitingConnection,
                configMXBean.getMaximumPoolSize(), configMXBean.getMinimumIdle(), configMXBean.getConnectionTimeout());
        System.err.println(logStr);
    }
}
